package org.wh1tew0lf.vframe.framework;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.wh1tew0lf.vframe.framework.vframeexception.VFailedToExcuteMethodException;
import org.wh1tew0lf.vframe.framework.vframeexception.VFrameException;
import org.wh1tew0lf.vframe.framework.vframeexception.VIllegalArgumentOfMethodException;
import org.wh1tew0lf.vframe.framework.vframeexception.VInputJSONObjectIsInvalidException;
import org.wh1tew0lf.vframe.framework.vframeexception.VInputJSONObjectIsNullException;
import org.wh1tew0lf.vframe.framework.vframeexception.VInvocationTargetException;
import org.wh1tew0lf.vframe.framework.vframeexception.VLogicModelManagerNotInitializedException;
import org.wh1tew0lf.vframe.framework.vframeexception.VLogicModelNotFoundException;
import org.wh1tew0lf.vframe.framework.vframeexception.VModelClassIsNullException;
import org.wh1tew0lf.vframe.framework.vframeexception.VModelKeyIsNullException;
import org.wh1tew0lf.vframe.framework.vframeexception.VModelMethodNotAccessibleException;
import org.wh1tew0lf.vframe.framework.vframeexception.VModelMethodNotFoundException;
import org.wh1tew0lf.vframe.framework.vframeexception.VModelNameIsNullException;
import org.wh1tew0lf.vframe.framework.vframeexception.VModelNotFoundException;
import org.wh1tew0lf.vframe.framework.vframeexception.VModuleNotAuthorizedException;
import org.wh1tew0lf.vframe.model.logicmodel.AbstractLogicModel;
import org.wh1tew0lf.vframe.model.logicmodel.ILogicModel;

public final class VLogicModelManager {

	private static ConcurrentHashMap<String, Class<? extends AbstractLogicModel>> logicModelClassMap;
	private static ConcurrentHashMap<String, String> logicModelKeyMap;

	static {
		logicModelClassMap = new ConcurrentHashMap<String, Class<? extends AbstractLogicModel>>();
		logicModelKeyMap = new ConcurrentHashMap<String, String>();
	}

	public static boolean updateLogicModelClass(String logicModelName,
			Class<? extends AbstractLogicModel> logicModelClass, String oldKey,
			String newKey) throws VFrameException {
		// TODO Log CALL TRACE
		if (logicModelName == null) {
			throw new VModelNameIsNullException();
		}
		if (logicModelClass == null) {
			throw new VModelClassIsNullException();
		}
		if (logicModelClassMap == null || logicModelKeyMap == null) {
			throw new VLogicModelManagerNotInitializedException();
		}
		if (newKey == null) {
			throw new VModelKeyIsNullException();
		}
		String storedKey = logicModelKeyMap.get(logicModelName);
		if (storedKey != null && (!storedKey.equals(oldKey))) {
			return false;
		}
		logicModelClassMap.put(logicModelName, logicModelClass);
		logicModelKeyMap.put(logicModelName, newKey);
		storedKey = null;
		return true;
	}

	public static boolean removeLogicModel(String logicModelName, String key)
			throws VFrameException {
		if (logicModelName == null) {
			throw new VModelNameIsNullException();
		}
		if (key == null) {
			throw new VModelKeyIsNullException();
		}
		String storedKey = logicModelKeyMap.get(logicModelName);
		if (storedKey == null) {
			throw new VModelNotFoundException(logicModelName);
		}
		if (storedKey.equals(key)) {
			logicModelKeyMap.remove(logicModelName);
			logicModelClassMap.remove(logicModelName);
			return true;
		} else {
			return false;
		}
	}

	public static ILogicModel getLogicModelInstance(String modelName,
			String moduleName, String moduleKey) throws VFrameException {
		if (modelName == null) {
			throw new VModelNameIsNullException();
		}
		if (!(VModuleManager.isAuthorizedModule(moduleName, moduleKey))) {
			throw new VModuleNotAuthorizedException(moduleName);
		}
		Class<? extends AbstractLogicModel> logicModelClass = logicModelClassMap
				.get(modelName);
		if (logicModelClass == null) {
			throw new VLogicModelNotFoundException(modelName);
		}
		Constructor<? extends AbstractLogicModel> logicModelConstructor = null;
		try {
			logicModelConstructor = logicModelClass
					.getDeclaredConstructor(new Class[] {});
			logicModelConstructor.setAccessible(true);
			ILogicModel logicModelInstance = logicModelConstructor
					.newInstance(new Object[] {});
			logicModelConstructor = null;
			return logicModelInstance;
		} catch (SecurityException e) {
			e.printStackTrace();
			throw new VModelMethodNotAccessibleException(modelName,
					"<constructor>");
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
			throw new VModelMethodNotFoundException(modelName, "<constructor>");
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			throw new VIllegalArgumentOfMethodException(modelName,
					"<constructor");
		} catch (InstantiationException e) {
			e.printStackTrace();
			throw new VFailedToExcuteMethodException(modelName, "<constructor>");
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			throw new VModelMethodNotAccessibleException(modelName,
					"<constructor>");
		} catch (InvocationTargetException e) {
			e.printStackTrace();
			throw new VInvocationTargetException(modelName, "<constructor>");
		}
	}

	protected static ArrayList<ILogicModel> parseFromJSONObject(JSONObject jo)
			throws VFrameException {
		if (jo == null) {
			throw new VInputJSONObjectIsNullException();
		}
		try {
			int totalNumber = jo.getInt("total");
			ArrayList<ILogicModel> resultArray = new ArrayList<ILogicModel>();
			if (totalNumber <= 0) {
				return resultArray;
			}
			JSONArray elementArray = jo.getJSONArray("items");
			JSONObject element = null;
			String modelName = null;
			for (int i = 0; i < totalNumber; i++) {
				element = elementArray.getJSONObject(i);
				modelName = element.getString("modelName");
				Class<? extends AbstractLogicModel> logicModelClass = logicModelClassMap
						.get(modelName);
				if (logicModelClass == null) {
					throw new VLogicModelNotFoundException(modelName);
				}
				Constructor<? extends AbstractLogicModel> logicModelConstructor = null;
				try {
					logicModelConstructor = logicModelClass
							.getDeclaredConstructor(new Class[] {});
					logicModelConstructor.setAccessible(true);
					ILogicModel logicModelInstance = logicModelConstructor
							.newInstance(new Object[] {});
					logicModelConstructor = null;
					logicModelInstance.parseJSONObject(element);
					resultArray.add(logicModelInstance);
				} catch (SecurityException e) {
					e.printStackTrace();
					throw new VModelMethodNotAccessibleException(modelName,
							"<constructor>");
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
					throw new VModelMethodNotFoundException(modelName,
							"<constructor>");
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
					throw new VIllegalArgumentOfMethodException(modelName,
							"<constructor");
				} catch (InstantiationException e) {
					e.printStackTrace();
					throw new VFailedToExcuteMethodException(modelName,
							"<constructor>");
				} catch (IllegalAccessException e) {
					e.printStackTrace();
					throw new VModelMethodNotAccessibleException(modelName,
							"<constructor>");
				} catch (InvocationTargetException e) {
					e.printStackTrace();
					throw new VInvocationTargetException(modelName,
							"<constructor>");
				}
				element = null;
				modelName = null;
			}
			elementArray = null;
			return resultArray;
		} catch (JSONException e) {
			e.printStackTrace();
			throw new VInputJSONObjectIsInvalidException(jo, e.getMessage());
		}
	}

	protected static boolean isAuthorizedLogicModel(String modelName,
			String modelKey) throws VFrameException {
		if (modelName == null) {
			throw new VModelNameIsNullException();
		}
		if (modelKey == null) {
			throw new VModelKeyIsNullException();
		}
		String storedKey = logicModelKeyMap.get(modelName);
		if (storedKey != null && storedKey.equals(modelKey)) {
			return true;
		} else {
			return false;
		}
	}
}
